home *** CD-ROM | disk | FTP | other *** search
/ Language/OS - Multiplatform Resource Library / LANGUAGE OS.iso / lisp / kcl / akcl / akcl1615.lha / merge.c < prev    next >
C/C++ Source or Header  |  1991-10-09  |  12KB  |  451 lines

  1. /* Copyright William F. Schelter, University of Texas 1987 */
  2.  
  3. /* This file may be copied by anyone, but changes may not
  4. be made without permission of the author.  The author hopes it will be
  5. useful but cannot assume any responsibility for its use or problems
  6. caused by its use.  The program is provided as is. */
  7.  
  8. /* The program takes two files file1 = orig and file2 = orig.V 
  9.  
  10. tutorial% merge orig orig.V > foo 
  11.  
  12. and copies orig according to the recipe in orig.V.  The advantage of
  13. this program is that it does this according to the context of orig.
  14. Thus even though orig might change slightly (eg some one added an
  15. extra line to the copyright notice), the same change file will
  16. probably still be valid.  
  17.  
  18. If the first argument is - then the orig is standard input.
  19. If a third argument is supplied, it is the name of a file to use
  20. instead of standard output.
  21.  
  22.  
  23. tutorial% merge orig orig.V | merge - change2 final
  24.  
  25. would take the result of merge of orig and orig.V and use it to merge
  26. with change2 to produce the file final.
  27.  
  28.  
  29. The format of a change (.V) file is very simple: There is only ONE
  30. type of command in a change file.  REPLACE X by Y.  Here X represents
  31. a chunk of text in the orig file, and Y the substitution which you
  32. wish to make for this occurrence.  The Y appears explicitly in the
  33. change file, while the text X may be specified fully and explicitly,
  34. OR by giving sufficient context from the beginning and end of X.  Thus
  35. in general it takes three things to specify a change.  The beginning
  36. of X (Xbegin), the end of X (Xend), and all of Y.  These three pieces
  37. of text are separated by four delimiters.  The delimiters are not
  38. single characters, but rather sequences of four characters.  This is
  39. done so as to avoid having to quote the delimiter (see QUOTING below).
  40. The delimiters are "\n@s[" "\n@s," "\n@s|" and "\n@s]". 
  41. NOTE: The \n (Newline) Character IS PART OF THE DELIMITER in ALL CASES.
  42.  
  43. @s[X
  44. @s|Y
  45. @s]
  46.  
  47. Thus in the above case the X text is only "X" it does not have any
  48. newlines in it! They belong to the delimiters.  For "X\n" we would see
  49.  
  50.  
  51. @s[X
  52.  
  53. @s|Y
  54. @s]
  55.  
  56.  
  57. The general case where X is a very long chunk of text, or perhaps something
  58. sensitive to copyright, so that you cannot include several pages, you 
  59. could make Xbegin be the first few lines, and Xend the last few lines.
  60. All intervening lines (including the Xbegin and the Xend, would be ripped
  61. out, and replaced by Y.
  62.  
  63. @s[Xbegin
  64. @s,Xend
  65. @s|Y
  66. @s]
  67.  
  68.  
  69. One cycle of the merge may be thought of as:
  70. The merge program looks in the change (.V) file for the next \ns[, 
  71. in order to determine  the next values for Xbegin,Xend,and Y.
  72. Having determined these, its position in the (.V) file will have 
  73. advanced to after the \n@s].
  74.  
  75. The merge program then starts at its current position in the original
  76. file and searches for the next occurrence of Xbegin, marking its
  77. beginning, then for the end of Xend.  The inclusive interval so
  78. marked, is deleted and Y is substituted. The current position in the
  79. original file is now at the end of the Xend text.  The next Xbegin
  80. text must occur after that point.  Only one pass is made through the
  81. files.  
  82.  
  83. It is an error if the start of Xend does not follow
  84. the end of Xbegin.  Thus Xbegin and Xend may NOT overlap.  A common
  85. case will be that Xbegin is the entire interval and Xend = ""
  86. In this case the merge program, if it finds \n@s| before \n@s,
  87. will assume you want Xend="".
  88.  
  89. EXAMPLES:
  90.  
  91. @s[Hi bill
  92. @s,
  93. @s|new body
  94. @s]
  95.  
  96. would delete the string "Hi bill" replacing it with "new body"
  97. Xbegin="Hi bill"
  98. Xend=""
  99. Interval = "Hi bill"
  100. Equivalently since the E interval is empty, we could have just
  101. omitted the \@s,
  102.  
  103. @s[Hi bill
  104. @s|new body
  105. @s]
  106.  
  107.  
  108. Example of change file with two changes:
  109. ****************
  110.  
  111. @s[(defmacro lcase (item &body body)
  112. @s,      (setq  v (car rest))
  113. @s|(defmacro lcase (items &body body)
  114.       (setq  v (cadr rest))
  115. @s]
  116.  
  117. Comments are allowed in change files.  In fact anything not between
  118. matching "\ns[" and "\ns]" is a comment. 
  119.  
  120.  
  121. @s[How is he
  122. @s,
  123. He is fine.
  124. @s|He is sick.
  125. @s]
  126.  
  127. *******
  128. end of change file
  129.  
  130. The first change would replace the interval of the original file
  131. "(defmacro....       (setq  v (car rest))"
  132. by
  133. "(defmacro lcase (items &body body)
  134.       (setq  v (cadr rest))"
  135.  
  136. If the program could not find the interval "(defmacro....  (setq v
  137. (car rest))" in the orignal file it would warn you. 
  138.  
  139. The intervals in the change file, must occur in the same order as in
  140. the original file.  There is an emacs program merge.el which can
  141. mechanically produce a changes file from an original and an edited
  142. version.
  143.  
  144. Note: For convenience we pretend that the change file starts with
  145. a new line, even if it does not.  Thus if @s[  are the first three
  146. characters of the file and CHSTART1 = \n@s[, we count this as a
  147. CHSTART1.  Since it is in the first column, it "appears" to have
  148. the new line there.
  149.  
  150.  
  151. QUOTING:
  152.  
  153. In order to have a change which involves one of the four letter
  154. delimiters given above, we use the convention that "\n@@" in the first
  155. column translates to "\n@".  You need not perform this quoting of @
  156. unless the merger would be confused.  For example \n@(defun .. would
  157. be ok, since this can't be mistaken for one of our delimiters.
  158. Nonetheless \n@@(defun or \n@@s[ would translate to have one @ sign,
  159. in the merge output.  The reason for not doubling all @ signs, is that
  160. it is very easy to scan (visually) a change you are constructing, to
  161. see that there are no @ signs in the first column, or at least none
  162. which could be confused for the four letter change delimiters
  163. "\n@s[","\n@s," ...  A poor human constructing a change (.V) file
  164. should not have to sort through the X or Y text adding quoting
  165. characters.
  166.  
  167. Note on length: Y may be any length, but Xbegin or Xend, may only be
  168. CONTEXT_LIMIT long.
  169.  
  170. */
  171.  
  172.  
  173.  
  174.  
  175.  
  176.  
  177.  
  178.  
  179.  
  180.  
  181. /******************  THE CODE ********************/
  182.  
  183.  
  184. #include <stdio.h>
  185.  
  186.  
  187. #define CONTEXT_LIMIT 3000 /* size of the longest delimiter or replacement */
  188.  
  189. char *malloc();
  190. void copy_rest();
  191. char ssearch_for_string();
  192.  
  193. #define NULL_OUT (FILE *)0
  194.  
  195. #define CHSTART1 "\n@s["
  196. #define CHSTART2 "\n@s,"
  197. #define CHSTART3 "\n@s|"
  198. #define CHSTART4 "\n@s]"
  199. #define ACCEPT ",|"
  200. #define NOACCEPT (char *) 0
  201. #define NUL '\0'
  202. #define TRUE 1
  203. #define FALSE 0
  204. #define eofch(ch) ((unsigned char)ch == (unsigned char) EOF)
  205.  
  206. char filenames[600];
  207.  
  208. #define myerror(string,arg) {(void)fprintf(stderr,string,arg); exit(-1);}
  209.  
  210. main(argc,argv)
  211. int argc;
  212. char *argv[];
  213. {FILE *orig,*changes,*out;
  214.  char *context,*endcontext;
  215.  char *origname,*altername,*outname;
  216.  char found;
  217.  context=malloc(CONTEXT_LIMIT+2);
  218.  endcontext=malloc(CONTEXT_LIMIT+2);
  219.  
  220.  outname=(char *)0;
  221.  
  222.  if (argc==1)
  223.    {int tem;
  224.     origname=filenames; altername=filenames+200; outname=filenames+400;
  225.      /* get names from stdin */
  226.      if (tem=scanf("%s %s %s",origname,altername,outname));
  227.             else myerror("Three args weren't supplied: scanf returned %d\n",tem);
  228.    }
  229.  else{  if (!((argc==3) || (argc==4)))
  230.        { myerror("Usage: merge file-orig file-changes [out-file]\n %d args given",argc-1);}
  231.  else
  232.    { origname=argv[1];
  233.      altername=argv[2];
  234.      if (argc >= 4) outname=argv[3];}};
  235.  
  236.  
  237. /* now we have the names either from command or stdin, so open files */
  238.  
  239. if(origname[0]=='-' && origname[1]==NULL)
  240.   orig=stdin;
  241.  else{
  242.  orig=fopen(origname,"r");
  243.  if (!orig) {perror(origname); exit(-1);};
  244. }
  245.  
  246.  changes=fopen(altername,"r");
  247.  if (!changes) {perror(origname); exit(-1);};
  248.  if (outname)
  249.    {out=fopen(outname,"w");
  250.     if (out); else {perror(outname); exit(-1);}}
  251.  else out=stdout;
  252.  
  253. /* check if the file starts with chstart1 - newline. to avoid 
  254. people thinking that starting file with @s[ is ok. */
  255.  {char *str = CHSTART1;
  256.   int ch;
  257.   while(*(++str)) /* skip the newline start */
  258.     { (ch=getc(changes));
  259.       if (ch == *str) ;
  260.       else
  261.       { ungetc(ch,changes); goto not_found;}
  262.     }
  263.    goto got_one;
  264.   not_found:;}
  265.  
  266.  {while(search_for_string(changes,CHSTART1,NULL_OUT,FALSE) > 0)
  267.     { got_one:
  268.       if (found=
  269.       ssearch_for_string(changes,CHSTART2,context,CONTEXT_LIMIT,TRUE,
  270.                  ACCEPT));
  271.       else
  272.     {myerror("\nNo end for start change context in change file:\n`%s'\n",context);};
  273.       if (found==ACCEPT[1])
  274.     *endcontext=NUL;
  275.       else
  276.     {
  277.       if /* there is probably a non null  endcontext */
  278.         (ssearch_for_string(changes,CHSTART3,endcontext, 
  279.                 CONTEXT_LIMIT,TRUE,NOACCEPT));
  280.       else
  281.         {myerror("No %s at beginning of line to denote end of change context",
  282.         CHSTART3);}};
  283. /* skip in orig down to the end of the context,copying thru begin context */
  284.       if (search_for_string(orig,context,out,FALSE)>0); 
  285.       else{myerror("\nCould not find the change start in original:\n`%s'\n"
  286.            ,context);};
  287.       if  /* copy out the changed version */
  288.     (search_for_string(changes,CHSTART4,out,TRUE)>0);
  289.       else
  290.     {myerror("No %s at beginning of line to denote end of change context",
  291.         CHSTART4);};
  292.          
  293. /*finish skipping over the region to be deleted in orig */    
  294.       {if( search_for_string(orig,endcontext,NULL_OUT,FALSE) > 0);
  295.        else
  296.      {myerror("\nCould not find the end of the change in original:\n`%s'\n",
  297.          endcontext);}}
  298.     }
  299.     copy_rest(orig,out);
  300.     return 0;
  301.   }}
  302.  
  303.  
  304. string_match(sta,stb)
  305. char *sta, *stb;
  306. {while(*sta!=0)
  307.    {if (*(sta++) != *(stb++)) return 0;}
  308.   if (*stb==0) return 1; else return 0;
  309.  }
  310.  
  311. void
  312. copy_rest(file,out)
  313. FILE *file,*out;
  314. {register int ch;
  315. while(1)
  316.  {  
  317.    ch=getc(file);
  318.    if (eofch(ch) && feof(file)) break;
  319.    putc(ch,out);}}
  320.  
  321. /* advance file to end of first occurrence of string, copying to out 
  322. until the beginning of string */
  323.  
  324. #define USE_UNQUOTE 1
  325.  
  326. search_for_string(file,string,out,unquoting)
  327. FILE *file,*out;
  328. char *string;
  329. int unquoting;
  330. {int result;
  331.  result=search_for_string1(file,string,out,USE_UNQUOTE && unquoting);
  332.  return result;}
  333.  
  334.  
  335. char *nxt,*lim,*ungetlim,*bp; 
  336. char buffer[CONTEXT_LIMIT];
  337.  
  338. /*
  339. void
  340. myungetc(ch)
  341. char ch;
  342. {*bp++ = ch;}
  343.  
  344. char
  345. mygetc(file)
  346. FILE *file; 
  347. {char x=((bp==buffer)? getc(file) : *--bp);
  348.  return x;
  349. }
  350. */
  351.  
  352. #define mygetc(file) ((bp==buffer)? getc(file) : *--bp)
  353. #define myungetc(ch) *bp++ = ch
  354.  
  355. search_for_string1(file,string,out,unquoting)
  356. FILE *file,*out;
  357. char *string;
  358. int unquoting;
  359. { /* char *nxt,*lim; */
  360.   char  *s;
  361.   int ch;
  362.   nxt=lim=(char *)0;
  363.  bp=buffer;
  364.  if (*string==NUL) return 1; unquoting;
  365.   while(1)
  366.   {begin:
  367.      ch=mygetc(file);
  368.    if ((eofch(ch)) &&(feof(file))) return 0;
  369.    if( ch==*string)
  370.      {                /* loop for checking */
  371.        s = string;
  372.        while(*(++s)!=0)
  373.      {(ch=mygetc(file));
  374.       if (eofch(ch) && feof(file))
  375.         {char *cp=string;while (cp++<=s)
  376.            {putc(*cp,file) ; return 0;}};
  377.       if (*s!=ch)
  378.         { if (out) putc(*string,out);
  379.           {char *cp=s;
  380.            if (!(unquoting && ch==string[1] && (s-string ==2)))
  381.                myungetc(ch);
  382.            while (--cp > string)
  383.          myungetc(*cp);
  384.           goto begin;}}
  385.     }
  386.         return 1;
  387.    /*    printf("<found one>");  */
  388.        }
  389.    else if (out) putc(ch,out);};
  390.   }
  391.  
  392.  
  393.  
  394. #define PUTC(ch,out) {if(ind++ < outlim) ((*(out++))=(ch));\
  395.                    else return -1;}
  396.  
  397. char
  398. ssearch_for_string1(file,string,out,outlim,unquoting,accept)
  399. int outlim,unquoting;
  400. FILE *file;
  401. char *out;
  402. char *string,*accept;
  403. {register int ch;
  404.  char *s; int ind=0;
  405.  if (*string==NUL) return 'a';
  406.  while(1)
  407.    {ch = getc(file);
  408.   begin:
  409.     if (feof(file)) return (char) 0;
  410.     if (ch==(*string))
  411.       {s=string; ind=0;
  412.        while(*(++s)!=0)
  413.      {if ((*s==(ch=getc(file)))
  414.           || (accept && *s==*accept && ch == *(accept+1)));
  415.       else
  416.         {if (out)
  417.            {char *cp; cp=string;
  418.          if (unquoting && ch==string[1] && (s-string ==2))  s--;
  419.         while (cp!=s)
  420.           {PUTC(*cp,out);cp++;}
  421.           }
  422.            break;}}
  423.        if(*s==0)
  424.       {PUTC(((char) 0),out);
  425.      /* found a match */
  426.        return(ch);}
  427.        else goto begin;}
  428.     else if (out) PUTC(ch,out);
  429.   }
  430. }
  431.  
  432. char
  433. ssearch_for_string(file,string,out1,outlim,unquoting,accept)
  434. int outlim;
  435. FILE *file;
  436. char *out1;
  437. char *string,*accept;
  438. int unquoting;
  439. {char result;
  440.  result=ssearch_for_string1(file,string,out1,outlim,unquoting,accept);
  441.  return result;}
  442.  
  443. /* 
  444. * To do:
  445. * 1)The buffering for mygetc could be more efficient (in local variable).
  446. * 2)Eliminate the double function calls used during debugging.
  447. * 3)Improve error message, for help in finding context if a change
  448. *   is not found.
  449. */
  450.